package hotciv.view;

import hotciv.framework.*;
import hotciv.view.figures.UnitFigure;

import java.awt.*;
import java.util.*;

import minidraw.framework.*;
import minidraw.standard.handlers.*;

/** UnitDrawing is a specialized Drawing (model component) from MiniDraw that
 * dynamically builds the list of Figures for MiniDraw to render from the
 * Unit objects that are visible (= on top of the unit stacks on each tile)
 * in the Game instance.
<#if type == "code">

<#include "/data/author.txt">
</#if>
*/

public class UnitDrawing implements Drawing, GameObserver {
  /** list of all figures currently selected */
  protected SelectionHandler selectionHandler;

  /** use a StandardDrawingChangeListenerHandler to handle all
  observer pattern subject role behavior */
  protected StandardDrawingChangeListenerHandler listenerHandler;

  /** my MiniDraw editor */
  protected DrawingEditor editor;
  /** the Game instance that this UnitDrawing is going to render units from */
  protected Game game;
  
  public UnitDrawing( DrawingEditor editor, Game game ) {
    this.editor = editor;
    this.game = game;
    listenerHandler = new StandardDrawingChangeListenerHandler();
    selectionHandler = new StandardSelectionHandler();
    
    game.addGameObserver(this);
    redefineUnitMap();
  }
  
  @Override
  public Figure add(Figure arg0) {
    throw new RuntimeException("Should not be used...");
  }

  @Override
  public Figure findFigure(int x, int y) {
    int row = y / GfxConstants.TILESIZE;
    int column = x / GfxConstants.TILESIZE;
    if ( row >= Game.WORLDSIZE || column >= Game.WORLDSIZE ||
        row < 0 || column < 0 ) {
      return null;
    }
    Position pos = new Position(row,column);

    UnitFigure figure;
    figure = (UnitFigure) figureMap.get(pos);
    return figure;
  }

  @Override
  public Iterator<Figure> iterator() {
    Collection<Figure> collection = figureMap.values();
    Iterator<Figure> i = collection.iterator();
    return i;
  }

  @Override
  public void lock() {
    // no concurrent updates are expected, thus this is silently ignored...
  }

  @Override
  public void unlock() {
    // no concurrent updates are expected, thus this is silently ignored...
  }

  @Override
  public Figure remove(Figure arg0) {
    throw new RuntimeException("remove Should not be used...");
  }


  @Override
  public void addToSelection(Figure arg0) {
    selectionHandler.addToSelection(arg0);
  }

  @Override
  public void clearSelection() {
    selectionHandler.clearSelection();
  }

  @Override
  public void removeFromSelection(Figure arg0) {
    selectionHandler.removeFromSelection(arg0);
  }

  @Override
  public java.util.List<Figure> selection() {
    return selectionHandler.selection();
  }

  @Override
  public void toggleSelection(Figure arg0) {
    selectionHandler.toggleSelection(arg0);
  }

  // === Delegation methods for the DrawingChangeListeners

  /**
   * Adds a listener for this drawing.
   */
  public void addDrawingChangeListener(DrawingChangeListener listener) {
    listenerHandler.addDrawingChangeListener(listener);
  }
  
  /**
   * Removes a listener from this drawing.
   */
  public void removeDrawingChangeListener(DrawingChangeListener listener) {
    listenerHandler.removeDrawingChangeListener(listener);
  }

  /**
   * Invalidates a rectangle and merges it with the
   * existing damaged area.
   * @see FigureChangeListener
   */
  public void figureInvalidated(FigureChangeEvent e) {
    listenerHandler.
      fireDrawingInvalidated( this, e.getInvalidatedRectangle() );
  }
 
  /**
   * Forces an update
   */
  public void figureRequestUpdate(FigureChangeEvent e) {
  }

  public void requestUpdate() {
    listenerHandler.fireDrawingRequestUpdate( this );
  }

  @Override
  public void figureChanged(FigureChangeEvent e) {
    // TODO Auto-generated method stub
    
  }

  @Override
  public void figureRemoved(FigureChangeEvent e) {
    // TODO Auto-generated method stub
    
  }

  @Override
  public void figureRequestRemove(FigureChangeEvent e) {
    // TODO Auto-generated method stub
    
  }
  
  /** store all figures visible in this drawing */
  Map<Position,Figure> figureMap = null;
   
  /** erase the old list of units, and build a completely new
   * one from scratch by iterating over the game world and add
   * Figure instances for each top-of-the-stack units in the world.
   */
  private void redefineUnitMap() {
    // ensure no units of the old list are accidental in
    // the selection!
    clearSelection();

    figureMap = new HashMap<Position,Figure>();
    Position p;
    for ( int r = 0; r < Game.WORLDSIZE; r++ ) {
      for ( int c = 0; c < Game.WORLDSIZE; c++ ) {
        p = new Position(r,c);
        Unit unit = game.getUnitAt(p);
        if ( unit != null ) {
          String type = unit.getTypeString();
          UnitFigure unitFigure =
            new UnitFigure( type, convertPositionToXY(p), unit);
          unitFigure.addFigureChangeListener(this);
          figureMap.put(p, unitFigure);
        }
      }
    }
  }

  /** Invoked whenever the Game instance updates the state of a tile */
  public void tileChanged(Position p) {
    // As I do not change any figures but instead deletes the old
    // figure list and build a new one, there are no 'dirty rectangle'
    // events sent to the drawing view. Thus when the requestUpdate
    // event reaches the drawing view it concludes that no redrawing
    // is necessary - which is WRONG! Therefore I have to send out
    // rectangle change events manually for the rectangles that
    // have become dirty.
    Point topleft = convertPositionToXY( p );
    // quick and dirty hack here! Extend the rectangle to ensure
    // that parts of the image that extend into neighbor tiles are redrawn.
    topleft.translate(-GfxConstants.TILESIZE, -GfxConstants.TILESIZE);
    Rectangle rect = new Rectangle(topleft, 
          new Dimension(3*GfxConstants.TILESIZE,3*GfxConstants.TILESIZE));
    // next, fire the event
    listenerHandler.fireDrawingInvalidated(this, rect);
    // rebuild the drawing of figures.
    redefineUnitMap();
    requestUpdate();
  }
  
  protected Point convertPositionToXY( Position p ) {
    return new Point(p.getColumn() * GfxConstants.TILESIZE, 
                     p.getRow() * GfxConstants.TILESIZE);
  }
  
}
